﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using SunXNA.RainbowXNA.Batch;

namespace SunXNA.RainbowXNA
{
    partial class CRainbowXNA
    {
        private class CRenderQuadTreeNode
        {
            #region Protected Member Variables

            protected CRenderQuadTreeNode m_parent;
            protected CRenderQuadTreeNode[] m_children;

            protected bool m_bIsLeaf;
            protected List<CRainbowXNA.CRenderInstance> m_data;

            protected BoundingBox m_bounds;

            #endregion

            #region Public Properties

            public bool IsLeaf
            {
                get
                {
                    return m_bIsLeaf;
                }
            }

            public BoundingBox Bounds
            {
                get
                {
                    return m_bounds;
                }
            }

            public int Count
            {
                get
                {
                    int count = 0;

                    if (m_bIsLeaf)
                    {
                        count += m_data.Count;
                    }
                    else
                    {
                        foreach (CRenderQuadTreeNode child in m_children)
                        {
                            count += child.Count;
                        }
                    }

                    return count;
                }
            }

            #endregion

            #region Public Member Functions

            /// <summary>
            /// Constructs the root of the QuadTree
            /// </summary>
            /// <param name="bounds">The BoundingBox that this Node represents</param>
            /// <param name="maxDepth">How deep the tree is allowed to go</param>
            public CRenderQuadTreeNode(BoundingBox bounds, int maxDepth)
            {
                m_parent = null;
                m_bounds = bounds;

                if (maxDepth > 0)
                {
                    m_bIsLeaf = false;

                    BoundingBox childbounds = bounds;
                    m_children = new CRenderQuadTreeNode[4];

                    //
                    //  TopLeft
                    //
                    childbounds.Min.X = bounds.Min.X;
                    childbounds.Min.Z = bounds.Min.Z;
                    childbounds.Max.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Max.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);

                    m_children[0] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    //
                    //  TopRight
                    //
                    childbounds.Min.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Min.Z = bounds.Min.Z;
                    childbounds.Max.X = bounds.Max.X;
                    childbounds.Max.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);
                    m_children[1] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    //
                    //  BottomLeft
                    //
                    childbounds.Min.X = bounds.Min.X;
                    childbounds.Min.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);
                    childbounds.Max.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Max.Z = bounds.Max.Z;
                    m_children[2] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    //
                    //  BottomRight
                    //
                    childbounds.Min.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Min.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);
                    childbounds.Max.X = bounds.Max.X;
                    childbounds.Max.Z = bounds.Max.Z;
                    m_children[3] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    m_data = null;
                }
                else
                {
                    m_bIsLeaf = true;
                    m_data = new List<CRainbowXNA.CRenderInstance>();
                    m_children = null;
                }
            }

            /// <summary>
            /// Adds an item of data to the Quad tree
            /// </summary>
            /// <param name="bounds">The BoundingBox that contains the data</param>
            /// <param name="data">The data to Add</param>
            /// <returns>The Leaf node that contains the object</returns>
            public void Add(BoundingBox bounds, CRainbowXNA.CRenderInstance data)
            {
                if (!m_bounds.Intersects(bounds))
                {
                    return;
                }

                if (m_bIsLeaf)
                {
                    m_data.Add(data);
                    return;
                }

                foreach (CRenderQuadTreeNode child in m_children)
                {
                    child.Add(bounds, data);
                }
            }

            /// <summary>
            /// Empties the QuadTree node of all data.
            /// The user will most likely only call this on the root node.
            /// </summary>
            public void Clear()
            {
                if (m_bIsLeaf)
                {
                    m_data.Clear();
                }
                else
                {
                    foreach (CRenderQuadTreeNode child in m_children)
                    {
                        child.Clear();
                    }
                }
            }

            /// <summary>
            /// Removes the specified data from the quad tree.
            /// The user should take care to call this on either:
            /// A) The LeafNode that contains the data OR
            /// B) The Root Node
            /// </summary>
            /// <param name="data">The data to remove</param>
            public void Remove(CRainbowXNA.CRenderInstance data)
            {
                if (m_bIsLeaf)
                {
                    while (m_data.Remove(data)) { }
                }
                else
                {
                    foreach (CRenderQuadTreeNode child in m_children)
                    {
                        child.Remove(data);
                    }
                }
            }

            public override string ToString()
            {
                string me = "";

                if (m_parent == null)
                {
                    me += "RootNode. ";
                }

                if (m_bIsLeaf)
                {
                    me += "LeafNode. ";
                }

                me += "Count = " + Count.ToString();

                me += "Bounds = " + m_bounds.ToString();

                return me;
            }

            #endregion

            #region Protected Member Functions

            /// <summary>
            /// Constructs a CRenderQuadTreeNode
            /// </summary>
            /// <param name="bounds">The BoundingBox that this Node represents</param>
            /// <param name="maxDepth">How deep the tree is allowed to go</param>
            /// <param name="parent">The parent of this node</param>
            protected CRenderQuadTreeNode(BoundingBox bounds, int maxDepth, CRenderQuadTreeNode parent)
            {
                m_parent = parent;
                m_bounds = bounds;

                if (maxDepth > 0)
                {
                    m_bIsLeaf = false;

                    BoundingBox childbounds = bounds;
                    m_children = new CRenderQuadTreeNode[4];

                    //
                    //  TopLeft
                    //
                    childbounds.Min.X = bounds.Min.X;
                    childbounds.Min.Z = bounds.Min.Z;
                    childbounds.Max.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Max.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);

                    m_children[0] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    //
                    //  TopRight
                    //
                    childbounds.Min.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Min.Z = bounds.Min.Z;
                    childbounds.Max.X = bounds.Max.X;
                    childbounds.Max.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);
                    m_children[1] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    //
                    //  BottomLeft
                    //
                    childbounds.Min.X = bounds.Min.X;
                    childbounds.Min.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);
                    childbounds.Max.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Max.Z = bounds.Max.Z;
                    m_children[2] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    //
                    //  BottomRight
                    //
                    childbounds.Min.X = bounds.Min.X + 0.5f * (bounds.Max.X - bounds.Min.X);
                    childbounds.Min.Z = bounds.Min.Z + 0.5f * (bounds.Max.Z - bounds.Min.Z);
                    childbounds.Max.X = bounds.Max.X;
                    childbounds.Max.Z = bounds.Max.Z;
                    m_children[3] = new CRenderQuadTreeNode(childbounds, maxDepth - 1, this);

                    m_data = null;
                }
                else
                {
                    m_bIsLeaf = true;
                    m_data = new List<CRainbowXNA.CRenderInstance>();
                    m_children = null;
                }
            }

            #endregion

            #region Draw Member Functions

            public int Draw(float timeInSeconds, BoundingFrustum frustrum, ref Matrix m4View, ref Matrix m4Projection, GraphicsDevice device, CBatchManager batchManager )
            {
                int objectsDrawn = 0;

                switch (frustrum.Contains(m_bounds))
                {
                    case ContainmentType.Contains:
                        {
                            objectsDrawn += DrawAll(timeInSeconds, ref m4View, ref m4Projection, device, batchManager);
                        }
                        break;

                    case ContainmentType.Intersects:
                        {
                            if (m_bIsLeaf)
                            {
                                objectsDrawn += DrawLeaf(timeInSeconds, frustrum, ref m4View, ref m4Projection, device, batchManager);
                            }
                            else
                            {
                                foreach (CRenderQuadTreeNode child in m_children)
                                {
                                    objectsDrawn += child.Draw(timeInSeconds, frustrum, ref m4View, ref m4Projection, device, batchManager);
                                }
                            }
                        }
                        break;

                    case ContainmentType.Disjoint:
                    default:
                        break;
                }

                return objectsDrawn;
            }

            protected int DrawAll(float timeInSeconds, ref Matrix m4View, ref Matrix m4Projection, GraphicsDevice device, CBatchManager batchManager)
            {
                int objectsDrawn = 0;

                if (m_bIsLeaf)
                {
                    foreach (CRainbowXNA.CRenderInstance instance in m_data)
                    {
                        if (timeInSeconds > instance.TimeStamp)
                        {
                            instance.TimeStamp = timeInSeconds;

                            //batchManager.Add(instance);
                            instance.ObjectHandle.RenderObject.AddToBatch(batchManager, instance);

                            //RenderObject.IBaseRenderObject renderObject = null;
                            //if (renderObjects.TryGetValue(instance.ObjectHandle.UID, out renderObject))
                            //{
                            //    ++objectsDrawn;
                            //    renderObject.Draw(device, instance.World, m4View, m4Projection);
                            //}
                        }
                    }
                }
                else
                {
                    foreach (CRenderQuadTreeNode child in m_children)
                    {
                        objectsDrawn += child.DrawAll(timeInSeconds, ref m4View, ref m4Projection, device, batchManager);
                    }
                }

                return objectsDrawn;
            }

            protected int DrawLeaf(float timeInSeconds, BoundingFrustum frustrum, ref Matrix m4View, ref Matrix m4Projection, GraphicsDevice device, CBatchManager batchManager)
            {
                int objectsDrawn = 0;

                Trace.Assert(m_bIsLeaf);

                foreach (CRainbowXNA.CRenderInstance instance in m_data)
                {
                    if (timeInSeconds > instance.TimeStamp)
                    {
                        if (frustrum.Contains(instance.GraphicsVolume) != ContainmentType.Disjoint)
                        {
                            instance.TimeStamp = timeInSeconds;
                            //batchManager.Add(instance);
                            instance.ObjectHandle.RenderObject.AddToBatch(batchManager, instance);

                            //RenderObject.IBaseRenderObject renderObject = null;
                            //if (renderObjects.TryGetValue(instance.ObjectHandle.UID, out renderObject))
                            //{
                            //    ++objectsDrawn;
                            //    renderObject.Draw(device, instance.World, m4View, m4Projection);
                            //}
                        }
                    }
                }

                return objectsDrawn;
            }

            #endregion
        }
    }
}
